package acewem.market;

import java.util.*;

import org.jfree.data.xy.XYSeries;

import acewem.market.ACEWEMmodel;

import edu.iastate.jrelm.core.SimpleAction;
import edu.iastate.jrelm.rl.rotherev.REPolicy;
import edu.iastate.jrelm.core.JReLMAgent;
import edu.iastate.jrelm.rl.ReinforcementLearner;
import edu.iastate.jrelm.rl.SimpleStatelessLearner;
import edu.iastate.jrelm.rl.rotherev.variant.VREParameters;


import sim.engine.*;


public class GenCo implements Steppable {
	
	private static final long serialVersionUID = 6628525522939315274L;

													/** Daily Net Earnings = sum of HourlyNetEarning over 24 hours  */
	private double dailyNetEarnings;   
													/** dailyProfit = sum of HourlyProfit over 24 hours   */
	private double dailyProfit;   
													/** Sum of 24 hourly Revenues */
	private double dailyRevenue;  
													/** Fixed cost of production  */
	private double fixedCost;
													/**  GenCo's accumulative money holding, money (new) = money(previous) + DailyProfit(new) (array)*/ 
	private double cumulativeWealth = 0d;
													/** GenCo's learning choice probability  */
	private double choiceProbability; 
													/** GenCo's learning choice propensity  */
	private double choicePropensity;   
													/** GenCo's learning choice ID  */
	private int    choiceID;   

	private String id;
	
	private int node;
	
	private Hashtable<String, Double> trueSupplyOffer = new Hashtable<String, Double>();
	private Hashtable<String, Double> learningData = new Hashtable<String, Double>();
	private Hashtable<String, Double> reportedSupplyOffer = new Hashtable<String, Double>();
	private Hashtable<String, Double> commitment = new Hashtable<String, Double>();
	private Hashtable<String, Double> lmpNode = new Hashtable<String, Double>();
	
	private VREParameters learningParams;
	private SimpleStatelessLearner learner;
  
	private ArrayList adListNew = new ArrayList(); 
	
	//this is public to avoid showing them in the GUI (no need for get() and set() methods). 
	public  XYSeries reportedSupplyOfferB;
	public  XYSeries reportedSupplyOfferA;
	public  XYSeries trueSupplyOfferB;
	public  XYSeries trueSupplyOfferA;
	
	public  XYSeries commitmentA;
	
	private ACEWEMmodel market;
	  	
//C-O-N-S-T-R-U-C-T-O-R-1-----------------------------------------------------------------------------------------------------------------------------  	
	public GenCo(String id, int randomseed, int node, double fcost, double money, double priceCap, ACEWEMmodel mt, Hashtable truesupplyoffer,Hashtable learningdata){
		
		this.id           		= id;
		this.node         		= node;
		this.cumulativeWealth   = money;
		this.fixedCost        	= fcost;
		this.trueSupplyOffer 	= truesupplyoffer;
		this.learningData 		= learningdata;
		this.market       		= mt;
		
		this.learningParams = new VREParameters(learningData.get("cooling"), learningData.get("experimentation"), learningData.get("propensity"), learningData.get("recency"), randomseed);	    	
			
		ArrayList adList = actionDomainConstruction(learningData.get("m1"), learningData.get("m2"), learningData.get("m3"),
													learningData.get("rIMaxL"), learningData.get("rIMaxU"), learningData.get("rIMinC"));		
		adListNew = checkActionDomain(adList, priceCap);
		this.learner = new SimpleStatelessLearner(learningParams, adListNew);
		
    	this.dailyProfit       = 0;
    	this.dailyNetEarnings  = 0;
    	this.dailyRevenue      = 0;   	
        
		if(market.isFromGUI)
		 {
			 reportedSupplyOfferB = new XYSeries("Reported Supply Offer B");
			 reportedSupplyOfferA = new XYSeries("Reported Supply Offer A");
			 trueSupplyOfferB = new XYSeries("True Supply Offer B");
			 trueSupplyOfferA =new XYSeries("True Supply Offer A");
		 }    
	}

//-------------------------------------------------------------------------------------------------------   
	private ArrayList checkActionDomain(ArrayList actionList, double priceCap){
		    ArrayList newActionList=new ArrayList();
		    for(int i=0; i<actionList.size(); i++){
		        double [] action=(double [])actionList.get(i);
		        double [] newAction=action.clone();
		        
		        if(!checkOverPriceCap(newAction, priceCap))
		            newActionList.add(newAction);
		    }
		    return newActionList;
		  }	
//-------------------------------------------------------------------------------------------------------   
/** This function is called by the learn method of the agent in order to learn! */
	private void updateDailyPerformance()
	{		
		Hashtable<String, Object> lmpatnode = new Hashtable<String, Object>();
		
		double commitmentHour=0d;
		double lmpHour=0d;
		double hourlyVariableCost=0d;
		double hourlyTotalCost=0d;
		double hourlyProfit=0d;
		double hourlyNetEarning=0d;
		double hourlyRevenue=0d;		
		
		for (int hour=0; hour<market.Hours; hour++){
									    	
			commitmentHour = commitment.get("commit"+hour);	
			//System.out.println(commitmentHour+this.getID());
			lmpatnode =(Hashtable)market.lmpAtHour.get("lmpathour"+hour);
			lmpHour = ((Double)lmpatnode.get("lmp"+node)).doubleValue();  // "lmp"+node makes sure that genco matches node

	    	hourlyVariableCost = commitmentHour*(trueSupplyOffer.get("aT") + trueSupplyOffer.get("bT") * commitmentHour);
	    	hourlyTotalCost    = hourlyVariableCost + fixedCost;
	    	hourlyRevenue      = commitmentHour * lmpHour;	      
	    	hourlyProfit	   = hourlyRevenue - hourlyTotalCost;
	    	hourlyNetEarning   = hourlyRevenue - hourlyVariableCost;
	        
	    	dailyProfit 	  += hourlyProfit;
	    	dailyNetEarnings  += hourlyNetEarning;
	    	dailyRevenue      += hourlyRevenue;				  			
		}   				
	}
	
//-----------------------------------------------------------------------------------------------------------------------------------------
    public void step(SimState state)
    {     	
    		submitReportedSupplyOffer();  //there is no learning at the very beggining!
   	}		
 

  

	/**
	 * @return the aR
	 */
	public double getaR() {
		return reportedSupplyOffer.get("aR");
	}

	/**
	 * @return the bR
	 */
	public double getbR() {
		return reportedSupplyOffer.get("bR");
	}

	/**
     * 
     */
    private void submitReportedSupplyOffer()
	{
		double[] triplet = (double[]) learner.chooseActionRaw();
		
		double aR     = triplet[0];		
		double bR     = triplet[1];        
		double capRU  = triplet[2];
		double capRL  = trueSupplyOffer.get("capTL"); 
		
		 if (id.equals("genco1")){
		 System.out.println(aR+" "+bR+"  "+capRU+" ");
		 }
		 
		reportedSupplyOffer.put("aR", aR);
		reportedSupplyOffer.put("bR", bR);
		reportedSupplyOffer.put("capRU", capRU);
		reportedSupplyOffer.put("capRL", capRL);
	}
			
    //------------------------------------------------------------------------------------------------------------------------------------------    
    /** The function checks whether reported supply offer is feasible or not 
     * @see <f/> DynTestAMES.JSLT.pdf (pg 34)  */    
    private boolean  checkOverPriceCap(double [] action, double priceCap){
        double lowerRI   = action[0];
        double upperRI   = action[1];
        double upperRCap = action[2];
        // Step 0: To get capMaxCalculated
        double capRU  = upperRCap  * (trueSupplyOffer.get("capTU") - trueSupplyOffer.get("capTL")) + trueSupplyOffer.get("capTL");

        // Step 1: To get lR
        double lR = (trueSupplyOffer.get("aT") + 2*trueSupplyOffer.get("bT")*trueSupplyOffer.get("capTL"))/(1 - lowerRI);

        // Step 2: To get uStart
        double u = trueSupplyOffer.get("aT") + 2*trueSupplyOffer.get("bT")*capRU;
        double uStart;
        if(lR < u){
          uStart = u;
        }
        else{
          uStart = lR + learningData.get("slopeStart");
        }

        if(uStart>=priceCap){
           return true;
            
        }
        
        // Step 3: To get uR
        double uR = uStart/(1 - upperRI);

        // Step 4: To get bReported
        action[1] = 0.5*((uR - lR)/(capRU - trueSupplyOffer.get("capTL")));

        // Step 5: To get aReported
        action[0] = lR - 2*action[1]*trueSupplyOffer.get("capTL");
         
        // for PriceCap
        double maxPrice=action[0]+2*action[1]*capRU;
        if(maxPrice>priceCap)
            action[2]=(priceCap-action[0])/(2*action[1]);
        else
            action[2]=capRU;
        
    	//if (genCoID == 2){
    	//	System.out.println("422genagentXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx");
    	//	System.out.println("  aR= "+action[0]+ "  br= "+action[1]+ "  capRU= "+ action[2]);
    	//	}
        
        
        return false;
    }   
//------------------------------------------------------------------------------------------------------------------------------------------      
    /** Constructs GenCo's action domain 
     * 
     * */
    private ArrayList<double[]> actionDomainConstruction(double m1, double m2, double m3,
    													 double rIMaxL, double rIMaxU, double rIMinC){

    	ArrayList<double[]> adMat = new ArrayList<double[]>();
    
    	//M = m1 * m2 * m3;

    	double inc1 = rIMaxL/(m1 - 1);
    	double inc2 = rIMaxU/(m2 - 1);
		double inc3 = (1-rIMinC)/(m3-1);
	
		for (double i = 0; i <= rIMaxL; i = i+inc1){
			for (double j = 0; j <= rIMaxU; j = j+inc2 ){
				for(double k = 1-rIMinC; k >= 0; k = k-inc3){  
				
					double[] alpha = new double[3]; // initialize triplet to zeros
					alpha[0] = i; 
					alpha[1] = j; 
	            	alpha[2] = k +rIMinC ; 
	            
	            	adMat.add(alpha);	            		
				}		
    		}
		}
	
		return adMat;
    }
    

//------------------------------------------------------------------------------------------------------------------------------------------    	
    /** Updates GenCo's accumulative money holding after energy trade - NOT USED*/
    private void updateWealth(double dailyProfit)
    {
    	this.cumulativeWealth = this.cumulativeWealth+dailyProfit;	
    }
    
//------------------------------------------------------------------------------------------------------------------------------------------    	   
      /** GenCo learning (updating propensity based on current period DailyProfit) - NOT USED*/
	  public void learn(){
		  
		this.updateDailyPerformance();//update my profitData before I learn
		this.updateWealth(dailyProfit); //update my money before I learn	
		SimpleAction lastAction = (SimpleAction) learner.getPolicy().getLastAction();
	    choiceID = lastAction.getID();
	    double[] act = (double[]) lastAction.getAct();
	    REPolicy policy = (REPolicy)learner.getPolicy();
	    if(learningData.get("rewardSelection")==0)// profit
	    learner.update(new Double(this.dailyProfit));

	    if(learningData.get("rewardSelection")==1)// net earnings
	    	learner.update(new Double(this.dailyNetEarnings));
	
	    choiceProbability = policy.getProbability(choiceID);
	    choicePropensity=policy.getPropensity(choiceID);
	    

	      //  updateActionProbabilities();
	      //  updateLearningResult();
	      //  updateDailyNetEarningResult();
	    
	  }

	/**
	 * @return the id
	 */
	public String getID() {
		return id;
	}

	/**
	 * @return the node
	 */
	public int getNode() {
		return node;
	}



	
	/**
	 * @return the commitment
	 */
	public Hashtable<String, Double> getCommitment() {
		return commitment;
	}

	/**
	 * @return the supplyOffer
	 */
	public Hashtable<String, Double> getSupplyOffer() {
		return trueSupplyOffer;
	}

	/**
	 * @return the reportedSupplyOffer
	 */
	public Hashtable<String, Double> getReportedSupplyOffer() {
		return reportedSupplyOffer;
	}

	/**
	 * @return the aT
	 */
	public double getaT() {
		return trueSupplyOffer.get("aT");
	}



	/**
	 * @return the bT
	 */
	public double getbT() {
		return trueSupplyOffer.get("bT");
	}


	public double getcommitment(int hour) {
		return commitment.get("commit"+hour);
	}





//------------------------------------------------------------------------------------------------------------------------------------------    	   
/*	  private void updateActionProbabilities(){
	      REPolicy policy = (REPolicy)learner.getPolicy();
	      double [] dProbability=policy.getProbabilities();
	      
	      boolean bConverged=true;
	      for(int i=0; i<adListNew.size(); i++){
	          oldActionProbability[i]=newActionProbability[i];
	          newActionProbability[i]=dProbability[i];
	         if((bConverged)&&(Math.abs(newActionProbability[i]-oldActionProbability[i])>dActionProbability)){
	              bConverged=false;
	              iCheckDayLengthCount=0;
	              bActionProbabilityConverge=false;
	          }
	      }
	      
	      if(bConverged){
	          iCheckDayLengthCount++;
	          if(iCheckDayLengthCount>iCheckDayLength)
	              bActionProbabilityConverge=true;
	      }
	 }
//------------------------------------------------------------------------------------------------------------------------------------------    	   
	  private void updateLearningResult(){
	      
	      boolean bConverged=true;
	      
	      if(Math.abs(oldLearningResult[0]-aR)>dLearningCheckDifference)
	          bConverged=false;
	      
	      if(Math.abs(oldLearningResult[1]-bR)>dLearningCheckDifference)
	          bConverged=false;
	      
	      if(Math.abs(oldLearningResult[2]-capRU)>dLearningCheckDifference)
	          bConverged=false;
	      
	      oldLearningResult[0]=aR;
	      oldLearningResult[1]=bR;
	      oldLearningResult[2]=capRU;
	      
	      if(bConverged){
	          iLearningCheckDayLengthCount++;
	          if(iLearningCheckDayLengthCount>iLearningCheckDayLength)
	              bLearningCheckConverge=true;
	      }
	      else{
	          iLearningCheckDayLengthCount=0;
	          bLearningCheckConverge=false;
	      }
	  }
	//------------------------------------------------------------------------------------------------------------------------------------------    	   

	  private void updateDailyNetEarningResult(){
	      
	      boolean bConverged=true;
	      
	      for(int i=0; i<iDailyNetEarningDayLengthCount; i++){
	          if(Math.abs(oldDailyNetEarningResult[i]-dailyNetEarnings)>dDailyNetEarningThreshold){
	              bConverged=false;
	              break;
	          }
	      }
	      
	      if(bConverged){
	          iDailyNetEarningDayLengthCount++;
	          if(iDailyNetEarningDayLengthCount>=iDailyNetEarningDayLength){
	              bDailyNetEarningConverge=true;
	              iDailyNetEarningDayLengthCount=iDailyNetEarningDayLength;
	              
	              for(int j=0; j<iDailyNetEarningDayLengthCount-1; j++){
	                  oldDailyNetEarningResult[j]=oldDailyNetEarningResult[j+1];
	              }
	              oldDailyNetEarningResult[iDailyNetEarningDayLengthCount-1]=dailyNetEarnings;
	          }
	          else
	              oldDailyNetEarningResult[iDailyNetEarningDayLengthCount-1]=dailyNetEarnings;
	      }
	      else{
	          oldDailyNetEarningResult[0]=dailyNetEarnings;
	          iDailyNetEarningDayLengthCount=0;
	          bDailyNetEarningConverge=false;
	      }
	  }
*/
	
	public String toString()
	{
		return this.id+ ", daily profit:" + this.dailyProfit + ", daily net earning:" + this.dailyNetEarnings +
		", daily revenue:" + this.dailyRevenue;
	}

	public void resetLerner() 
	{
		this.dailyNetEarnings=0d;
		this.dailyProfit=0d;
		this.dailyRevenue=0d;
		this.learner = new SimpleStatelessLearner(learningParams, adListNew);
		
	}
	
	

} 

   













